跳到主要内容

Go 套接字 Socket 编程 - 快速使用

net 包地址

这里就不过多介绍这个包了,这个包属于 Golang 提供的网络底层包了,一般情况只是使用 Dial, Listen, Accept, Conn 方法(接口),下面介绍下它的用法

TCP 连接

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

简单来说一个 TCP 客户端进行 TCP 通信的流程如下:

  1. 建立与服务端的链接
  2. 进行数据收发
  3. 关闭链接

Server 端

注意,这个监听的 IP 地址要填本机的 局域网/外网 地址才能监听到其它主机发来的请求,如果是 127.0.0.1 只能监听到本地的服务

package main

import (
"bufio"
"fmt"
"net"
)

func process(conn net.Conn) {
// 处理完关闭连接
defer conn.Close()

// 针对当前连接做发送和接受操作
for {
reader := bufio.NewReader(conn)
var buf [128]byte
n, err := reader.Read(buf[:])
if err != nil {
fmt.Printf("read from conn failed, err:%v\n", err)
break
}

recv := string(buf[:n])
fmt.Printf("收到的数据:%v\n", recv)

// 将接受到的数据返回给客户端
_, err = conn.Write([]byte("ok"))
if err != nil {
fmt.Printf("write from conn failed, err:%v\n", err)
break
}
}
}

func main() {
// 建立 tcp 服务
listen, err := net.Listen("tcp", "127.0.0.1:9090")
if err != nil {
fmt.Printf("listen failed, err:%v\n", err)
return
}

for {
// 等待客户端建立连接
conn, err := listen.Accept()
if err != nil {
fmt.Printf("accept failed, err:%v\n", err)
continue
}
// 启动一个单独的 goroutine 去处理连接
go process(conn)
}
}

Client 端

package main

import (
"bufio"
"fmt"
"net"
"os"
"strings"
)

func main() {
// 1、与服务端建立连接
conn, err := net.Dial("tcp", "127.0.0.1:9090")
if err != nil {
fmt.Printf("conn server failed, err:%v\n", err)
return
}
// 2、使用 conn 连接进行数据的发送和接收
input := bufio.NewReader(os.Stdin)
for {
s, _ := input.ReadString('\n')
s = strings.TrimSpace(s)
if strings.ToUpper(s) == "Q" {
return
}

_, err = conn.Write([]byte(s))
if err != nil {
fmt.Printf("send failed, err:%v\n", err)
return
}
// 从服务端接收回复消息
var buf [1024]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read failed:%v\n", err)
return
}
fmt.Printf("收到服务端回复:%v\n", string(buf[:n]))
}
}

UDP 发送数据

UDP 协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。

Server 端

package main

import (
"fmt"
"net"
)

func main() {
// 建立 udp 服务器
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9090,
})

if err != nil {
fmt.Printf("listen failed error:%v\n", err)
return
}
defer listen.Close() // 使用完关闭服务

for {
// 接收数据
var data [1024]byte
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Printf("read data error:%v\n", err)
return
}
fmt.Printf("addr:%v\t count:%v\t data:%v\n", addr, n, string(data[:n]))
// 发送数据
_, err = listen.WriteToUDP(data[:n], addr)
if err != nil {
fmt.Printf("send data error:%v\n", err)
return
}
}
}

Client 端

package main

import (
"fmt"
"net"
)

func main() {
// 建立服务
listen, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 9090,
})
if err != nil {
fmt.Printf("listen udp server error:%v\n", err)
}
defer listen.Close()

// 发送数据
sendData := []byte("Hello server")
_, err = listen.Write(sendData) // 发送数据
if err != nil {
fmt.Println("发送数据失败,err:", err)
return
}

// 接收数据
data := make([]byte, 4096)
n, remoteAddr, err := listen.ReadFromUDP(data) // 接收数据
if err != nil {
fmt.Println("接收数据失败,err:", err)
return
}
fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

con.Read 和 ioutil.ReadAll 区别

在 socket-tcp 的代码中大致这两种接受数据的方法,con.Read 以及 ioutil.ReadAll,这两种方法的区别是什么,以及他们的使用方法。

// 在socket中,Read方法直接读取一段指定长度的字节,当然这个方法也是阻塞的,如果对面没有发送数据,他会一直等到超时,
func (c *conn) Read(b []byte) (int, error)

// 他一直读取直到读取到err或eof,就是出错或者到文件结束标志,一般用来一次性获取所有的流数据。
reader := bufio.NewReader(conn)
ioutil.ReadAll(reader)

这两个方法 con.Readioutil.ReadAll 都可以读取,但是 ioutil.ReadAll 要收到 error 或者 EOF 才会返回,也就是说如果发送端发送数据后,调用 Close 关闭连接,不等待服务端的返回数据,服务端可以用 ioutil.ReadAll 来读取数据,它可以判断出 EOF 后读取结束。

但如果客户端发送数据后,没有关闭,而是等待服务端的数据返回,用 ReadAll 是不行的,长连接交互的话就用 Read。

Reference

Write to Client UDP Socket in Go Socket Server 套接字级编程 Go 语言使用 net 包实现 Socket 网络编程 golang socket中tcp中的ioutil.ReadAll阻塞的问题